/* 0.2 => 0.3 
* 增加 指令解析器 Compile 
* 所有需要遍历 绑定节点所有的子节点 并真正的支持 {{}} 模板功能，
* 支持双向绑定。
* 支持 mounted 以及 methods 还有 真正的 data
* vue =>  Bue
*/
function propsHijack(data,key,val){
  observe(val); //深度劫持 对象所有属性
  var dep = new Dep()
  Object.defineProperty(data,key,{
    enumerable:true, // 允许枚举
    configurable:true, // 允许配置属性
    //1. 只要后面订阅者 [[get]] 了data, 当前对应属性的 Watcher实例就会添加到Dep队列中
    get() {
      if (Dep.target) { // Watcher存在的话 添加订阅者
        dep.addSub(Dep.target); // 这里添加一个订阅者
      }
      return val
    }, 
    //2. 一旦有任何一个属性变动了，就触发整体更新 
    set(newVal) {
      if (val === newVal) return 
      val = newVal
      console.log(`属性 ${key} 已经被监听了，现在他的值为 ${newVal.toString()}`)
      dep.notify() // 如果数据变化，通知*所有*订阅者
    }
  })
}

/* 监听器 */     
function observe(data){
  // 没有属性了，或属性不是对象了时 便退出遍历。
  if (!data || (typeof data !== 'object')) { 
    return
  }
  Object.keys(data).forEach( key => {
    propsHijack(data,key,data[key])
  })
}


// 订阅者管理器 dep (发布者)
class Dep {
  subs = [] // Watcher 订阅者 队列
  static target = null // 缓存当前的 watcher
  addSub(sub) {
    this.subs.push(sub)
  }
  notify(){
    this.subs.forEach(sub => {
      sub.update() // 通知每个订阅者检查更新
    })
  }
}

/* 
* 将 data，methods, el 都注入 option中
* 订阅者 watcher 不绑定在 bue 中。 而是动态绑定在 使用模板 以及指令的各属性上。
*/
class Bue {
  constructor (option) {
    this.data = option.data
    this.methods = option.methods
    // 遍历data 劫持data所有的属性
    observe(this.data)
    // 再给data包一层 get set 使得实例可以直接访问到 具体属性，而不用通过 data
    // 只用浅绑定 一层 就对了 不用写data 目的就达到了 => this.form.xxx
    // 其实也是个诡计 就是把data里的属性 给拷贝了份 放到 实例本身上了而已，并且使其不可被枚举
    // 修改 这些属性 data内部的属性也会跟着改变的（响应）。（注意 get set）
    Object.keys(this.data).forEach( key=> {
      Object.defineProperty(this, key, {
        enumerable:false,
        configurable:true,
        get() {
          return this.data[key]
        },
        set(newVal) {
          return this.data[key] = newVal
        }
      })
    })
    // 启动指令模板解析器
    new Compile(option.el, this)
    // 解析完成后， 就相当于 挂载完 调用mounted
    // 这里 call 不 call 都一样 放松，不用太专牛角尖
    option.mounted.call(this)
  }
}
